/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.web.portlet.visitor.impl;

import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;

import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.basics.exception.technical.TechnicalIOException;
import com.genia.toolbox.constants.client.Charset;
import com.genia.toolbox.web.portlet.bean.Portlet;
import com.genia.toolbox.web.portlet.bean.impl.AbstractPortlet;
import com.genia.toolbox.web.portlet.bean.impl.ContainerPortlet;
import com.genia.toolbox.web.portlet.bean.impl.DispatcherPortlet;
import com.genia.toolbox.web.portlet.bean.impl.HolePortlet;
import com.genia.toolbox.web.portlet.bean.impl.LinkedPortlet;
import com.genia.toolbox.web.portlet.bean.impl.SimplePortlet;
import com.genia.toolbox.web.portlet.bean.impl.TemplatePortlet;
import com.genia.toolbox.web.portlet.visitor.PortletVisitor;

/**
 * this visitor writes a spring config file describing a <code>Portlet</code>
 * into an <code>Appendable</code>.
 */
public class SpringWriterPortletVisitor
    implements PortletVisitor
{

  /**
   * the spring config file prefix.
   */
  private static final String PREFIX = "<?xml version=\"1.0\" encoding=\""
      + Charset.UTF8
      + "\"?>\n\t<beans xmlns=\"http://www.springframework.org/schema/beans\"\n\t\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t\txsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd\">\n";

  /**
   * the spring config file suffix.
   */
  private static final String SUFFIX = "</beans>";

  /**
   * the <code>Appendable</code> to which the config file is written.
   */
  private final transient Appendable appendable;

  /**
   * the name of the spring bean describing the portlet being converted.
   */
  private final transient String name;

  /**
   * Wether the first portlet is begin written or not.
   */
  private transient boolean first = true;



  /**
   * constructor.
   * 
   * @param appendable
   *          the <code>Appendable</code> to which the config file is written
   * @param name
   *          the name of the spring bean describing the portlet being converted
   */
  public SpringWriterPortletVisitor(final Appendable appendable, final String name)
  {
    this.appendable = appendable;
    this.name = name;
  }



  /**
   * the visitor method for a <code>ContainerPortlet</code>.
   * 
   * @param portlet
   *          the <code>ContainerPortlet</code> begin visited
   * @throws BundledException
   *           if an error occurred
   * @see com.genia.toolbox.web.portlet.visitor.PortletVisitor#visitSimplePortlet(com.genia.toolbox.web.portlet.bean.impl.SimplePortlet)
   */
  public void visitContainerPortlet(final ContainerPortlet portlet)
      throws BundledException
  {
    final boolean first = this.first;
    prefixForAbstractPortlet(portlet);
    this.first = false;

    appendProperty("viewName", portlet.getViewName());

    if (!portlet.getSubPortlets().isEmpty()) {
      append("<property name='subPortlets'><list>");
      for (final Portlet subPortlet : portlet.getSubPortlets()) {
        subPortlet.visit(this);
      }
      append("</list></property>");
    }

    suffixForPortlet(first);
  }



  /**
   * the visitor method for a <code>DispatcherPortlet</code>.
   * 
   * @param portlet
   *          the <code>DispatcherPortlet</code> begin visited
   * @throws TechnicalIOException
   *           if an error occurred
   * @see com.genia.toolbox.web.portlet.visitor.PortletVisitor#visitDispatcherPortlet(com.genia.toolbox.web.portlet.bean.impl.DispatcherPortlet)
   */
  public void visitDispatcherPortlet(final DispatcherPortlet portlet)
      throws TechnicalIOException
  {
    final boolean first = this.first;
    prefixForAbstractPortlet(portlet);
    this.first = false;
    appendProperty("url", portlet.getUrl());
    suffixForPortlet(first);
  }



  /**
   * the visitor method for a <code>SimplePortlet</code>.
   * 
   * @param portlet
   *          the <code>SimplePortlet</code> begin visited
   * @throws TechnicalIOException
   *           if an error occurred
   * @see com.genia.toolbox.web.portlet.visitor.PortletVisitor#visitSimplePortlet(com.genia.toolbox.web.portlet.bean.impl.SimplePortlet)
   */
  public void visitSimplePortlet(final SimplePortlet portlet)
      throws TechnicalIOException
  {
    final boolean first = this.first;
    prefixForAbstractPortlet(portlet);
    this.first = false;
    appendProperty("controllerName", portlet.getControllerName());
    appendMap("parameters", portlet.getParameters());

    suffixForPortlet(first);
  }



  /**
   * append the <code>CharSequence</code> to the <code>Appendable</code>.
   * 
   * @param charSequence
   *          the <code>CharSequence</code> begin appended.
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void append(final CharSequence charSequence)
      throws TechnicalIOException
  {
    try {
      appendable.append(charSequence);
    }
    catch (final IOException e) {
      throw new TechnicalIOException(e);
    }
  }



  /**
   * append a CDATA value to the result.
   * 
   * @param cdata
   *          the CDATA to append
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void appendCDATA(final String cdata)
      throws TechnicalIOException
  {
    append("<![CDATA[");
    append(cdata);
    append("]]>");
  }



  /**
   * append a property of type <code>Map&lt;String, String&gt;</code> to the
   * result.
   * 
   * @param name
   *          the name of the property
   * @param map
   *          the value of the property
   * @throws TechnicalIOException
   *           when an error occurred
   */
  private void appendMap(final String name, final Map<String, String> map)
      throws TechnicalIOException
  {
    if (!map.isEmpty()) {
      append("<property name='");
      append(name);
      append("'><map>");
      for (final Entry<String, String> entry : map.entrySet()) {
        append("<entry><key><value>");
        appendCDATA(entry.getKey());
        append("</value></key><value>");
        appendCDATA(entry.getValue());
        append("</value></entry>");
      }
      append("</map></property>");
    }
  }



  /**
   * append a property to the result.
   * 
   * @param name
   *          the name of the property
   * @param value
   *          the value of the property
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void appendProperty(final String name, final String value)
      throws TechnicalIOException
  {
    if (value != null) {
      append("<property name='");
      append(name);
      append("'><value>");
      appendCDATA(value);
      append("</value></property>");
    }
  }



  /**
   * append the prefix xml fragment common to all <code>Portlet</code>. It
   * also append the xml prefix if the portlet being converted is the first one.
   * 
   * @param portlet
   *          the portlet being converted
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void prefixForPortlet(final Portlet portlet)
      throws TechnicalIOException
  {
    if (first) {
      append(PREFIX);
    }

    append("<bean ");
    if (first) {
      append("name='");
      append(name);
      append("' ");
    }
    append("class='");
    append(portlet.getClass().getCanonicalName());
    append("'>");
  }



  /**
   * append the prefix xml fragment common to all <code>AbstractPortlet</code>.
   * It also append the xml prefix if the portlet being converted is the first
   * one.
   * 
   * @param portlet
   *          the portlet being converted
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void prefixForAbstractPortlet(final AbstractPortlet portlet)
      throws TechnicalIOException
  {
    prefixForPortlet(portlet);
    appendProperty("identifier", portlet.getIdentifier());
    appendProperty("descriptionName", portlet.getDescriptionName());
    appendMap("attributes", portlet.getAttributes());
  }



  /**
   * append the suffix xml fragment common to all <code>Portlet</code>. It
   * also append the xml suffix if the portlet being converted is the first one.
   * 
   * @param first
   *          is the portlet being converted the first one
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void suffixForPortlet(final boolean first)
      throws TechnicalIOException
  {
    append("</bean>");

    if (first) {
      append(SUFFIX);
    }
  }



  /**
   * the visitor method for a <code>LinkedPortlet</code>.
   * 
   * @param portlet
   *          the <code>LinkedPortlet</code> begin visited
   * @throws TechnicalIOException
   *           if an error occurred
   */
  public void visitLinkedPortlet(final LinkedPortlet portlet)
      throws TechnicalIOException
  {
    final boolean first = this.first;
    prefixForPortlet(portlet);
    this.first = false;
    appendProperty("linkedPortletName", portlet.getLinkedPortletName());
    suffixForPortlet(first);
  }



  /**
   * the visitor method for a <code>HolePortlet</code>.
   * 
   * @param portlet
   *          the <code>HolePortlet</code> begin visited
   * @throws TechnicalIOException
   *           if an error occurred
   */
  public void visitHolePortlet(final HolePortlet portlet)
      throws TechnicalIOException
  {
    final boolean first = this.first;
    prefixForPortlet(portlet);
    this.first = false;
    appendProperty("holeName", portlet.getHoleName());
    suffixForPortlet(first);
  }



  /**
   * the visitor method for a <code>TemplatePortlet</code>.
   * 
   * @param portlet
   *          the <code>TemplatePortlet</code> begin visited
   * @throws TechnicalIOException
   *           if an error occurred
   */
  public void visitTemplatePortlet(final TemplatePortlet portlet)
      throws TechnicalIOException
  {
    final boolean first = this.first;
    prefixForPortlet(portlet);
    this.first = false;
    appendProperty("linkedPortletName", portlet.getLinkedPortletName());
    appendMap("holesPortlets", portlet.getHolesPortlets());
    suffixForPortlet(first);
  }

}
